#include "../fmt.h"

namespace sfc::fmt {

#define U(n) u64(1e##n)

template <class T>
struct UInt;

template <class T>
UInt(T) -> UInt<T>;

template <>
struct UInt<u32> {
  static constexpr u64 POW10[] = {U(0), U(1), U(2), U(3), U(4), U(5), U(6), U(7), U(8), U(9)};
  u32 _0;

  [[sfc_inline]] auto count_bin(u32 NBIT) const -> u32 {
    const auto n = sizeof(u32) * 8 - __builtin_clz(_0);
    return (n + NBIT - 1) / NBIT;
  }

  [[sfc_inline]] auto count_dcm() const -> u32 {
    const auto s = __builtin_clz(_0 | 1);
    const auto n = (sizeof(u32) * 8 - s) * 1233 >> 12;
    const auto r = n + 1 - ((_0 | 1) < POW10[n] ? 1 : 0);
    return r;
  }
};

template <>
struct UInt<u64> {
  static constexpr u64 POW10[] = {U(0),  U(1),  U(2),  U(3),  U(4),  U(5),  U(6),
                                  U(7),  U(8),  U(9),  U(10), U(11), U(12), U(13),
                                  U(14), U(15), U(16), U(17), U(18), U(19)};

  u64 _0;

  [[sfc_inline]] auto count_bin(u32 NBIT) const -> u32 {
    const auto n = sizeof(u64) * 8 - __builtin_clzll(_0);
    return (n + NBIT - 1) / NBIT;
  }

  [[sfc_inline]] auto count_dcm() const -> u32 {
    const auto s = __builtin_clzll(_0 | 1);
    const auto n = (sizeof(u64) * 8 - s) * 1233 >> 12;
    const auto r = n + 1 - ((_0 | 1) < POW10[n] ? 1 : 0);
    return r;
  }
};

struct FmtInt {
  char* _buf;
  usize _cap;
  usize _len;
  const Style& _style;

  FmtInt(Slice<char> buf, const Style& style)
      : _buf{buf._ptr}, _cap{buf._len}, _len{0}, _style{style} {}

  auto to_str() const -> Str {
    return {_buf, _len};
  }

  void _write_chars(char val, u32 cnt) {
    for (auto end = _len + cnt; _len != end; ++_len) {
      _buf[_len] = val;
    }
  }

  void _write_sign(bool is_neg) {
    if (is_neg) {
      _buf[_len++] = '-';
      return;
    }

    switch (_style._sign) {
      case '-':
        _buf[_len++] = ' ';
        break;
      case '+':
        _buf[_len++] = '+';
        break;
      default:
        break;
    }
  }

  void _write_prefix() {
    if (_style._prefix != '#') {
      return;
    }
    _buf[_len++] = '0';
    switch (_style._type) {
      case 'b':
      case 'B':
      case 'x':
      case 'X':
        _buf[_len++] = _style._type;
      default:
        break;
    }
  }

  void _write_bin(auto val, u32 cnt, u32 RADIX) {
    static const auto DIGITS =                                  //
        _style._type ? "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"   // upcase
                     : "0123456789abcdefghijklmnopqrstuvwxyz";  // lowercase

    const auto b = _buf + _len - 1;
    for (auto p = b + cnt; p != b; --p, val /= RADIX) {
      *p = DIGITS[val & (RADIX - 1)];
    }
    _len += cnt;
  }

  void _write_dcm(auto val, u32 cnt) {
    static const auto DIGITS =
        "0001020304050607080910111213141516171819"
        "2021222324252627282930313233343536373839"
        "4041424344454647484950515253545556575859"
        "6061626364656667686970717273747576777879"
        "8081828384858687888990919293949596979899";

    auto b = _buf + _len - 1;
    auto p = b + cnt;
    for (; val >= 100; val /= 100) {
      *p-- = DIGITS[(val % 100) * 2 + 1];
      *p-- = DIGITS[(val % 100) * 2 + 0];
    }
    if (val < 10) {
      *p-- = '0' + char(val);
    } else {
      *p-- = DIGITS[val * 2 + 1];
      *p-- = DIGITS[val * 2 + 0];
    }
    while (p != b) {
      *p-- = '0';
    }
    _len += cnt;
  }

  void pad_bin(auto val, bool is_neg, u32 NBIT) {
    const auto RADIX = 2u << NBIT;

    const auto cnt = UInt{val}.count_bin(NBIT);
    const auto len = cmp::min<u32>(_style._width, _cap - 4);

    if (len <= cnt) {
      this->_write_sign(is_neg);
      this->_write_prefix();
      this->_write_bin(val, cnt, RADIX);
      return;
    }

    const auto fcnt = len - cnt;
    if (_style._prefix == '#') {
      this->_write_sign(is_neg);
      this->_write_prefix();
      this->_write_chars('0', fcnt);
      this->_write_bin(val, cnt, RADIX);
      return;
    }

    const auto fill = _style.fill(' ');
    switch (_style._align) {
      default:
      case '>':
        this->_write_chars(fill, fcnt);
        this->_write_sign(is_neg);
        this->_write_bin(val, cnt, RADIX);
        break;
      case '<':
        this->_write_sign(is_neg);
        this->_write_bin(val, cnt, RADIX);
        this->_write_chars(fill, fcnt);
        break;
      case '^':
        this->_write_chars(fill, (fcnt + 0) / 2);
        this->_write_sign(is_neg);
        this->_write_bin(val, cnt, RADIX);
        this->_write_chars(fill, (fcnt + 1) / 2);
        break;
      case '=':
        this->_write_sign(is_neg);
        this->_write_chars(fill, fcnt);
        this->_write_bin(val, cnt, RADIX);
        break;
    }
  }

  void pad_dcm(auto val, bool is_neg) {
    const auto cnt = UInt{val}.count_dcm();
    const auto len = cmp::min<u32>(_style._width, sizeof(_buf) - 4);

    if (len <= cnt) {
      this->_write_sign(is_neg);
      this->_write_dcm(val, cnt);
      return;
    }

    const auto fcnt = len - cnt;
    const auto fill = _style.fill(' ');

    switch (_style._align) {
      default:
      case '>':
        this->_write_chars(fill, fcnt);
        this->_write_sign(is_neg);
        this->_write_dcm(val, cnt);
        break;
      case '<':
        this->_write_sign(is_neg);
        this->_write_dcm(val, cnt);
        this->_write_chars(fill, fcnt);
        break;
      case '^':
        this->_write_chars(fill, (fcnt + 0) / 2);
        this->_write_sign(is_neg);
        this->_write_dcm(val, cnt);
        this->_write_chars(fill, (fcnt + 1) / 2);
        break;
      case '=':
        this->_write_sign(is_neg);
        this->_write_chars(fill, fcnt);
        this->_write_dcm(val, cnt);
        break;
    }
  }

  void pad_int(auto val, bool is_neg) {
    switch (_style._type) {
      default:
        return this->pad_dcm(val, is_neg);
      case 'b':
      case 'B':
        return this->pad_bin(val, is_neg, 1);
      case 'o':
      case 'O':
        return this->pad_bin(val, is_neg, 3);
      case 'x':
      case 'X':
        return this->pad_bin(val, is_neg, 4);
      case 'c':
        _buf[_len++] = char(val);
    }
  }
};

template <>
auto Style::_pad_num(u32 val, Slice<char> buf) const -> Str {
  auto f = FmtInt{buf, *this};
  f.pad_int(val, false);
  return f.to_str();
}

template <>
auto Style::_pad_num(u64 val, Slice<char> buf) const -> Str {
  auto f = FmtInt{buf, *this};
  f.pad_int(val, false);
  return f.to_str();
}

template <>
auto Style::_pad_num(i32 val, Slice<char> buf) const -> Str {
  auto f = FmtInt{buf, *this};
  f.pad_int(u32(val < 0 ? -val : +val), val < 0);
  return f.to_str();
}

template <>
auto Style::_pad_num(i64 val, Slice<char> buf) const -> Str {
  auto f = FmtInt{buf, *this};
  f.pad_int(u64(val < 0 ? -val : +val), val < 0);
  return f.to_str();
}

template <>
auto Style::_pad_num(const void* val, Slice<char> buf) const -> Str {
  auto s = *this;
  s._type = this->type('X');
  s._width = this->width(12);
  if (s._prefix == 0 && this->_type != 0) {
    s._prefix = '#';
  }
  auto f = FmtInt{buf, s};
  f.pad_int(u64(val), false);
  return f.to_str();
}

}  // namespace sfc::fmt
